package at.fhj.itm10.ticketmaster.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import at.fhj.itm10.ticketmaster.entities.Entity;

/**
 * Abstract DAO.
 * 
 * @author Wolfgang Gaar
 */
/* package private */ abstract class AbstractDAO {

	private Connection connection;
	
	private DateFormat dateFormatter;
	
	/**
	 * Used to manage all given model objects.
	 */
	private Map<Long, Entity> rowMap;

	public AbstractDAO(Connection connection) throws DAOException {
		this.connection = connection;
		this.dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		this.rowMap = new HashMap<Long, Entity>();
		
		if (connection == null) {
			throw new DAOException("connection is null");
		}
	}
	
	/**
	 * Saves an entity into the identity hashmap.
	 * 
	 * @param id
	 * @param entity
	 */
	protected void saveEntity(long id, Entity entity) {
		if (id <= 0) {
			throw new IllegalArgumentException();
		}
		
		if (entity == null) {
			throw new NullPointerException();
		}
		
		rowMap.put(id, entity);
	}
	
	/**
	 * Get an entity from the identity hashmap. Returns null if nothing was found.
	 * 
	 * @param id
	 * @return Model or NULL
	 */
	protected Entity retrieveEntity(long id) {
		if (rowMap.containsKey(id)) {
			return rowMap.get(id);
		}
		
		return null;
	}
	
	/**
	 * Removes an entity from the identity hashmap.
	 * 
	 * @param id
	 */
	protected void removeEntity(long id) {
		if (rowMap.containsKey(id)) {
			rowMap.remove(id);
		}
	}
	
	/**
	 * Check if a given entity is valid.
	 * 
	 * @param entity
	 */
	protected void checkForValidEntity(Entity entity) {
		if (entity == null) {
			throw new NullPointerException();
		}
	}
	
	/**
	 * Prepares a statement correctly.
	 * 
	 * @param sql
	 * @return PreparedStatement
	 * @throws DAOException
	 */
	protected PreparedStatement prepareStatement(String sql) throws DAOException {
		try {
			// , PreparedStatement.RETURN_GENERATED_KEYS
			return connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
		} catch (SQLException e) {
			throw new DAOException(e);
		}
	}
	
	/**
	 * Retrieves the id of an inserted entity.
	 * 
	 * @param statement
	 * @return long
	 * @throws DAOException
	 */
	protected long retrieveInsertId(PreparedStatement statement) throws DAOException {
		long id = 0;
		
		try {
			ResultSet generatedKeys = statement.getGeneratedKeys();
			
			if (generatedKeys.next()) {
				id = generatedKeys.getLong(1);
				generatedKeys.close();
			}
		} catch (SQLException e) {
			throw new DAOException(e);
		}
		
		return id;
	}

	/**
	 * Post-processing of INSERT queries.
	 * 
	 * @param statement
	 * @param entity
	 * @throws DAOException
	 */
	protected void processInsert(PreparedStatement statement, Entity entity) throws DAOException {
		try {
			statement.executeUpdate();

			long insertId = retrieveInsertId(statement);
			entity.setId(insertId);
			saveEntity(insertId, entity);

			statement.close();
		} catch (SQLException e) {
			throw new DAOException(e);
		}
	}
	
	/**
	 * Post-processing of UPDATE queries.
	 * 
	 * @param statement
	 * @param entity
	 * @throws DAOException
	 */
	protected void processUpdate(PreparedStatement statement, Entity entity) throws DAOException {
		try {
			statement.executeUpdate();
			statement.close();
		} catch (SQLException e) {
			throw new DAOException(e);
		}
	}

	/**
	 * Processing of DELETE queries.
	 * 
	 * @param statement
	 * @param entity
	 * @throws DAOException
	 */
	public void doDelete(PreparedStatement statement, Entity entity) throws DAOException {
		try {
			statement.setLong(1, entity.getId());
			statement.executeUpdate(); // really... executeUpdate() for DELETE queries...

			entity.setId(0);
			removeEntity(entity.getId());

			statement.close();
		} catch (SQLException e) {
			throw new DAOException(e);
		}
	}

	/**
	 * Finds one entity. Also sets the first statement field to the supplied id. 
	 * 
	 * @param id
	 * @param statement
	 * @return Model
	 * @throws DAOException
	 */
	public Entity doFindOne(long id, PreparedStatement statement) throws DAOException {
		ResultSet resultSet = null;
		Entity entity = null;

		try {
			statement.setLong(1, id);

			resultSet = statement.executeQuery();
			

			if (resultSet.next()) {
				entity = fillEntityByResult(resultSet);
				saveEntity(id, entity);
			}
			
			
		} catch (SQLException e) {
			throw new DAOException(e);
		} finally {
			try {
				resultSet.close();
			} catch (Exception e) {
				throw new DAOException("");
			}
		}
		
		return entity;
	}

	/**
	 * Finds more entities.
	 * 
	 * @param primaryKeyName
	 * @param statement
	 * @return T
	 * @throws DAOException
	 */
	@SuppressWarnings("unchecked")
	public <T extends Entity> List<T> doFindMore(String primaryKeyName, PreparedStatement statement) throws DAOException {
		List<T> entities = new ArrayList<T>();

		try {
			ResultSet resultSet = statement.executeQuery();
			
			while (resultSet.next()) {
				// Get entity if it is already registered
				long id = resultSet.getLong(primaryKeyName);
				Entity entity = retrieveEntity(id);

				// Get entity from result set and save it
				if (entity == null) {
					entity = fillEntityByResult(resultSet);
					saveEntity(id, entity);
				}

				entities.add((T) entity);
			}

			resultSet.close();
		} catch (SQLException e) {
			throw new DAOException(e);
		}

		return entities;
	}
	
	/**
	 * Converts a Date into a String
	 * 
	 * @param date
	 * @return String
	 * @throws ParseException
	 */
	protected String convertDateToString(Date date) throws ParseException {
		if (date == null) {
			throw new IllegalArgumentException("date is null");
		}
		
		return dateFormatter.format(date);
	}
	
	/**
	 * Converts a String into a Date
	 * 
	 * @param date
	 * @return Date
	 * @throws ParseException
	 */
	protected Date convertStringToDate(String date) throws ParseException {
		if (date == null) {
			throw new IllegalArgumentException("date string is null");
		}
		
		return dateFormatter.parse(date);
	}

	/**
	 * Method used to fill an entity using a result set.
	 * 
	 * @param resultSet the filled ResultSet
	 * @return the filled Model
	 * @throws DAOException
	 */
	protected abstract Entity fillEntityByResult(ResultSet resultSet) throws DAOException;

}
